Calcul symbolique en Python


In [6]:
%matplotlib inline

Introduction

Ce notebook est la traduction française du cours sur SymPy disponible entre autre sur Wakari avec quelques modifications et compléments notamment pour la résolution d'équations différentielles. Il a pour but de permettre aux étudiants de différents niveaux d'expérimenter des notions mathématiques en leur fournissant une base de code qu'ils peuvent modifier.

SymPy - est un module Python qui peut être utilisé dans un programme Python ou dans une session IPython. Il fournit de puissantes fonctionnalités de calcul symbolique.

Pour commencer à utiliser SymPy dans un programme ou un notebook Python, importer le module sympy:


In [4]:
from sympy import *

Pour obtenir des sorties mathématiques formatées $\LaTeX$ :


In [8]:
from sympy import init_printing
init_printing(use_latex=True)

Variables symboliques

Dans SymPy on a besoin de créer des symboles pour les variables qu'on souhaite employer. Pour cela on utilise la class Symbol:


In [9]:
x = Symbol('x')

In [10]:
(pi + x)**2


Out[10]:
$$\left(x + \pi\right)^{2}$$

In [11]:
# manière alternative de définir plusieurs symboles en une seule instruction
a, b, c = symbols("a, b, c")

On peut ajouter des contraintes sur les symboles lors de leur création :


In [12]:
x = Symbol('x', real=True)

In [13]:
x.is_imaginary


Out[13]:
False

In [6]:
x = Symbol('x', positive=True)

In [15]:
x > 0


Out[15]:
$$\mathrm{True}$$

Nombres complexes

L'unité imaginaire est notée I dans Sympy.


In [16]:
1+1*I


Out[16]:
$$1 + i$$

In [17]:
I**2


Out[17]:
$$-1$$

In [18]:
(1 + x * I)**2


Out[18]:
$$\left(i x + 1\right)^{2}$$

Nombres rationnels

Il y a trois types numériques différents dans SymPy : Real (réel), Rational (rationnel), Integer (entier) :


In [19]:
r1 = Rational(4,5)
r2 = Rational(5,4)

In [20]:
r1


Out[20]:
$$\frac{4}{5}$$

In [21]:
r1+r2


Out[21]:
$$\frac{41}{20}$$

In [22]:
r1/r2


Out[22]:
$$\frac{16}{25}$$

Evaluation numérique

SymPy permet une précision arbitraire des évaluations numériques et fournit des expressions pour quelques constantes comme : pi, E, oo pour l'infini.

Pour évaluer numériquement une expression nous utilisons la fonction evalf (ou N). Elle prend un argument n qui spécifie le nombre de chiffres significatifs.


In [23]:
pi.evalf(n=50)


Out[23]:
$$3.1415926535897932384626433832795028841971693993751$$

In [24]:
E.evalf(n=4)


Out[24]:
$$2.718$$

In [25]:
y = (x + pi)**2

In [26]:
N(y, 5) # raccourci pour evalf


Out[26]:
$$\left(x + 3.1416\right)^{2}$$

Quand on évalue des expressions algébriques on souhaite souvent substituer un symbole par une valeur numérique. Dans SymPy cela s'effectue par la fonction subs :


In [27]:
y.subs(x, 1.5)


Out[27]:
$$\left(1.5 + \pi\right)^{2}$$

In [28]:
N(y.subs(x, 1.5))


Out[28]:
$$21.5443823618587$$

La fonction subs permet de substituer aussi des symboles et des expressions :


In [29]:
y.subs(x, a+pi)


Out[29]:
$$\left(a + 2 \pi\right)^{2}$$

On peut aussi combiner l'évolution d'expressions avec les tableaux de NumPy (pour tracer une fonction par ex) :


In [2]:
import numpy

In [7]:
x_vec = numpy.arange(0, 10, 0.1)
y_vec = numpy.array([N(((x + pi)**2).subs(x, xx)) for xx in x_vec])


Out[7]:
0.0

In [32]:
import matplotlib.pyplot as plt

In [33]:
fig, ax = plt.subplots()
ax.plot(x_vec, y_vec);


Manipulations algébriques

Une des principales utilisations d'un système de calcul symbolique est d'effectuer des manipulations algébriques d'expression. Il est possible de développer un produit ou bien de factoriser une expression. Les fonctions pour réaliser ces opérations de bases figurent dans les exemples des sections suivantes.

Développer et factoriser

Les premiers pas dans la manipulation algébrique


In [34]:
(x+1)*(x+2)*(x+3)


Out[34]:
$$\left(x + 1\right) \left(x + 2\right) \left(x + 3\right)$$

In [35]:
expand((x+1)*(x+2)*(x+3))


Out[35]:
$$x^{3} + 6 x^{2} + 11 x + 6$$

La fonction expand (développer) prend des mots clés en arguments pour indiquer le type de développement à réaliser. Par exemple pour développer une expression trigonomètrique on utilise l'argument trig=True :


In [36]:
sin(a+b)


Out[36]:
$$\sin{\left (a + b \right )}$$

In [37]:
expand(sin(a+b), trig=True)


Out[37]:
$$\sin{\left (a \right )} \cos{\left (b \right )} + \sin{\left (b \right )} \cos{\left (a \right )}$$

In [38]:
sin(a+b)**3


Out[38]:
$$\sin^{3}{\left (a + b \right )}$$

In [39]:
expand(sin(a+b)**3, trig=True)


Out[39]:
$$\sin^{3}{\left (a \right )} \cos^{3}{\left (b \right )} + 3 \sin^{2}{\left (a \right )} \sin{\left (b \right )} \cos{\left (a \right )} \cos^{2}{\left (b \right )} + 3 \sin{\left (a \right )} \sin^{2}{\left (b \right )} \cos^{2}{\left (a \right )} \cos{\left (b \right )} + \sin^{3}{\left (b \right )} \cos^{3}{\left (a \right )}$$

Lancer help(expand) pour une explication détaillée des différents types de développements disponibles.

L'opération opposée au développement est bien sur la factorisation qui s'effectue grâce à la fonction factor :


In [40]:
factor(x**3 + 6 * x**2 + 11*x + 6)


Out[40]:
$$\left(x + 1\right) \left(x + 2\right) \left(x + 3\right)$$

In [41]:
x1, x2 = symbols("x1, x2")

In [42]:
factor(x1**2*x2 + 3*x1*x2 + x1*x2**2)


Out[42]:
$$x_{1} x_{2} \left(x_{1} + x_{2} + 3\right)$$

Simplify

The simplify tries to simplify an expression into a nice looking expression, using various techniques. More specific alternatives to the simplify functions also exists: trigsimp, powsimp, logcombine, etc.

The basic usages of these functions are as follows:


In [43]:
# simplify expands a product
simplify((x+1)*(x+2)*(x+3))


Out[43]:
$$\left(x + 1\right) \left(x + 2\right) \left(x + 3\right)$$

In [44]:
# simplify uses trigonometric identities
simplify(sin(a)**2 + cos(a)**2)


Out[44]:
$$1$$

In [45]:
simplify(cos(x)/sin(x))


Out[45]:
$$\frac{1}{\tan{\left (x \right )}}$$

simplify permet aussi de tester l'égalité d'expressions :


In [46]:
exp1 = sin(a+b)**3
exp2 = sin(a)**3*cos(b)**3 + 3*sin(a)**2*sin(b)*cos(a)*cos(b)**2 + 3*sin(a)*sin(b)**2*cos(a)**2*cos(b) + sin(b)**3*cos(a)**3
simplify(exp1 - exp2)


Out[46]:
$$0$$

In [47]:
if simplify(exp1 - exp2) == 0:
    print "{0} = {1}".format(exp1, exp2)
else:
    print "exp1 et exp2 sont différentes"


sin(a + b)**3 = sin(a)**3*cos(b)**3 + 3*sin(a)**2*sin(b)*cos(a)*cos(b)**2 + 3*sin(a)*sin(b)**2*cos(a)**2*cos(b) + sin(b)**3*cos(a)**3

apart and together

Pour manipuler des expressions numériques de fractions on dispose des fonctions apart and together :


In [48]:
f1 = 1/((a+1)*(a+2))

In [49]:
f1


Out[49]:
$$\frac{1}{\left(a + 1\right) \left(a + 2\right)}$$

In [50]:
apart(f1)


Out[50]:
$$- \frac{1}{a + 2} + \frac{1}{a + 1}$$

In [51]:
f2 = 1/(a+2) + 1/(a+3)

In [52]:
f2


Out[52]:
$$\frac{1}{a + 3} + \frac{1}{a + 2}$$

In [53]:
together(f2)


Out[53]:
$$\frac{2 a + 5}{\left(a + 2\right) \left(a + 3\right)}$$

Simplify combine les fractions mais ne factorise pas :


In [54]:
simplify(f2)


Out[54]:
$$\frac{2 a + 5}{\left(a + 2\right) \left(a + 3\right)}$$

Calcul

En plus des manipulations algébriques, l'autre grande utilisation d'un système de calcul symbolique et d'effectuer des calculs comme des dérivées et intégrales d'expressions algébriques.

Dérivation

La dérivation est habituellement simple. On utilise la fonction diff avec pour premier argument l'expression à dériver et comme second le symbole de la variable suivant laquelle dériver :


In [55]:
y


Out[55]:
$$\left(x + \pi\right)^{2}$$

Dérivée première


In [56]:
diff(y**2, x)


Out[56]:
$$4 \left(x + \pi\right)^{3}$$

Pour des dérivées d'ordre supérieur :


In [57]:
diff(y**2, x, x) # dérivée seconde


Out[57]:
$$12 \left(x + \pi\right)^{2}$$

In [58]:
diff(y**2, x, 2) # dérivée seconde avec une autre syntaxe


Out[58]:
$$12 \left(x + \pi\right)^{2}$$

Pour calculer la dérivée d'une expression à plusieurs variables :


In [59]:
x, y, z = symbols("x,y,z")

In [60]:
f = sin(x*y) + cos(y*z)

$\frac{d^3f}{dxdy^2}$


In [61]:
diff(f, x, 1, y, 2)


Out[61]:
$$- x \left(x y \cos{\left (x y \right )} + 2 \sin{\left (x y \right )}\right)$$

Integration

L'intégration est réalisée de manière similaire :


In [62]:
f


Out[62]:
$$\sin{\left (x y \right )} + \cos{\left (y z \right )}$$

In [63]:
integrate(f, x)


Out[63]:
$$x \cos{\left (y z \right )} + \begin{cases} 0 & \text{for}\: y = 0 \\- \frac{1}{y} \cos{\left (x y \right )} & \text{otherwise} \end{cases}$$

En fournissant des limites pour la variable d'intégration on peut évaluer des intégrales définies :


In [64]:
integrate(f, (x, -1, 1))


Out[64]:
$$2 \cos{\left (y z \right )}$$

et aussi des intégrales impropres pour lesquelles on ne connait pas de primitive


In [65]:
x_i = numpy.arange(-5, 5, 0.1)
y_i = numpy.array([N((exp(-x**2)).subs(x, xx)) for xx in x_i])
fig2, ax2 = plt.subplots()
ax2.plot(x_i, y_i)
ax2.set_title("$e^{-x^2}$")


Out[65]:
<matplotlib.text.Text at 0x13180a20>

In [66]:
integrate(exp(-x**2), (x, -oo, oo))


Out[66]:
$$\sqrt{\pi}$$

Rappel, oo est la notation SymPy pour l'infini.

Sommes et produits

On peut évaluer les sommes et produits d'expression avec les fonctions Sum et Product :


In [67]:
n = Symbol("n")

In [68]:
Sum(1/n**2, (n, 1, 10))


Out[68]:
$$\sum_{n=1}^{10} \frac{1}{n^{2}}$$

In [69]:
Sum(1/n**2, (n,1, 10)).evalf()


Out[69]:
$$1.54976773116654$$

In [70]:
Sum(1/n**2, (n, 1, oo)).evalf()


Out[70]:
$$1.64493406684823$$

In [71]:
N(pi**2/6) # fonction zeta(2) de Riemann


Out[71]:
$$1.64493406684823$$

Les produits sont calculés de manière très semblables :


In [72]:
Product(n, (n, 1, 10)) # 10!


Out[72]:
$$\prod_{n=1}^{10} n$$

Limites

Les limites sont évaluées par la fonction limit. Par exemple :


In [73]:
limit(sin(x)/x, x, 0)


Out[73]:
$$1$$

On peut changer la direction d'approche du point limite par l'argument du mot clé dir :


In [74]:
limit(1/x, x, 0, dir="+")


Out[74]:
$$\infty$$

In [75]:
limit(1/x, x, 0, dir="-")


Out[75]:
$$-\infty$$

Séries

Le développement en série est une autre fonctionnalité très utile d'un système de calcul symbolique. Dans SymPy on réalise les développements en série grâce à la fonction series :


In [76]:
series(exp(x), x)


Out[76]:
$$1 + x + \frac{x^{2}}{2} + \frac{x^{3}}{6} + \frac{x^{4}}{24} + \frac{x^{5}}{120} + \mathcal{O}\left(x^{6}\right)$$

Par défaut le développement de l'expression s'effectue au voisinage de $x=0$, mais on peut développer la série au voisinage de toute autre valeur de $x$ en incluant explicitement cette valeur lors de l'appel à la fonction :


In [77]:
series(exp(x), x, 1)


Out[77]:
$$e + e \left(x - 1\right) + \frac{e}{2} \left(x - 1\right)^{2} + \frac{e}{6} \left(x - 1\right)^{3} + \frac{e}{24} \left(x - 1\right)^{4} + \frac{e}{120} \left(x - 1\right)^{5} + \mathcal{O}\left(\left(x - 1\right)^{6}; x\rightarrow1\right)$$

Et on peut explicitement définir jusqu'à quel ordre le développement doit être mené :


In [78]:
series(exp(x), x, 1, 10)


Out[78]:
$$e + e \left(x - 1\right) + \frac{e}{2} \left(x - 1\right)^{2} + \frac{e}{6} \left(x - 1\right)^{3} + \frac{e}{24} \left(x - 1\right)^{4} + \frac{e}{120} \left(x - 1\right)^{5} + \frac{e}{720} \left(x - 1\right)^{6} + \frac{e}{5040} \left(x - 1\right)^{7} + \frac{e}{40320} \left(x - 1\right)^{8} + \frac{e}{362880} \left(x - 1\right)^{9} + \mathcal{O}\left(\left(x - 1\right)^{10}; x\rightarrow1\right)$$

Le développement en séries inclue l'ordre d'approximation. Ceci permet de gérer l'ordre du résultat de calculs utilisant des développements en séries d'ordres différents :


In [79]:
s1 = cos(x).series(x, 0, 5)
s1


Out[79]:
$$1 - \frac{x^{2}}{2} + \frac{x^{4}}{24} + \mathcal{O}\left(x^{5}\right)$$

In [80]:
s2 = sin(x).series(x, 0, 2)
s2


Out[80]:
$$x + \mathcal{O}\left(x^{2}\right)$$

In [81]:
expand(s1 * s2)


Out[81]:
$$x + \mathcal{O}\left(x^{2}\right)$$

Si on ne souhaite pas afficher l'ordre on utilise la méthode removeO :


In [82]:
expand(s1.removeO() * s2.removeO())


Out[82]:
$$\frac{x^{5}}{24} - \frac{x^{3}}{2} + x$$

Mais cela conduit à des résultats incorrects pour des calculs avec plusieurs développements :


In [83]:
(cos(x)*sin(x)).series(x, 0, 6)


Out[83]:
$$x - \frac{2 x^{3}}{3} + \frac{2 x^{5}}{15} + \mathcal{O}\left(x^{6}\right)$$

Plus sur les séries

Algèbre linéaire

Matrices

Les matrices sont définies par la classe Matrix :


In [84]:
m11, m12, m21, m22 = symbols("m11, m12, m21, m22")
b1, b2 = symbols("b1, b2")

In [85]:
A = Matrix([[m11, m12],[m21, m22]])
A


Out[85]:
$$\left[\begin{matrix}m_{11} & m_{12}\\m_{21} & m_{22}\end{matrix}\right]$$

In [86]:
b = Matrix([[b1], [b2]])
b


Out[86]:
$$\left[\begin{matrix}b_{1}\\b_{2}\end{matrix}\right]$$

Avec les instances de la classe Matrix on peut faire les opérations algébriques classiques :


In [87]:
A**2


Out[87]:
$$\left[\begin{matrix}m_{11}^{2} + m_{12} m_{21} & m_{11} m_{12} + m_{12} m_{22}\\m_{11} m_{21} + m_{21} m_{22} & m_{12} m_{21} + m_{22}^{2}\end{matrix}\right]$$

In [88]:
A * b


Out[88]:
$$\left[\begin{matrix}b_{1} m_{11} + b_{2} m_{12}\\b_{1} m_{21} + b_{2} m_{22}\end{matrix}\right]$$

Et calculer les déterminants et inverses :


In [89]:
A.det()


Out[89]:
$$m_{11} m_{22} - m_{12} m_{21}$$

In [90]:
A.inv()


Out[90]:
$$\left[\begin{matrix}\frac{1}{m_{11}} + \frac{m_{12} m_{21}}{m_{11}^{2} \left(m_{22} - \frac{m_{12} m_{21}}{m_{11}}\right)} & - \frac{m_{12}}{m_{11} \left(m_{22} - \frac{m_{12} m_{21}}{m_{11}}\right)}\\- \frac{m_{21}}{m_{11} \left(m_{22} - \frac{m_{12} m_{21}}{m_{11}}\right)} & \frac{1}{m_{22} - \frac{m_{12} m_{21}}{m_{11}}}\end{matrix}\right]$$

Résolution d'équations

Pour résoudre des équations et des systèmes d'équations on utilise la fonction solve :


In [91]:
solve(x**2 - 1, x)


Out[91]:
$$\left [ -1, \quad 1\right ]$$

In [92]:
solve(x**4 - x**2 - 1, x)


Out[92]:
$$\left [ - i \sqrt{- \frac{1}{2} + \frac{\sqrt{5}}{2}}, \quad i \sqrt{- \frac{1}{2} + \frac{\sqrt{5}}{2}}, \quad - \sqrt{\frac{1}{2} + \frac{\sqrt{5}}{2}}, \quad \sqrt{\frac{1}{2} + \frac{\sqrt{5}}{2}}\right ]$$

In [93]:
expand((x-1)*(x-2)*(x-3)*(x-4)*(x-5))


Out[93]:
$$x^{5} - 15 x^{4} + 85 x^{3} - 225 x^{2} + 274 x - 120$$

In [94]:
solve(x**5 - 15*x**4 + 85*x**3 - 225*x**2 + 274*x - 120, x)


Out[94]:
$$\left [ 1, \quad 2, \quad 3, \quad 4, \quad 5\right ]$$

Système d'équations :


In [95]:
solve([x + y - 1, x - y - 1], [x,y])


Out[95]:
$$\left \{ x : 1, \quad y : 0\right \}$$

En termes d'autres expressions symboliques :


In [96]:
solve([x + y - a, x - y - c], [x,y])


Out[96]:
$$\left \{ x : \frac{a}{2} + \frac{c}{2}, \quad y : \frac{a}{2} - \frac{c}{2}\right \}$$

Résolution d'équations différentielles

Pour résoudre des équations diférentielles et des systèmes d'équations différentielles on utilise la fonction dsolve :


In [97]:
from sympy import Function, dsolve, Eq, Derivative, sin, cos, symbols
from sympy.abc import x

Exemple d'équation différentielle du 2e ordre


In [98]:
f = Function('f')
dsolve(Derivative(f(x), x, x) + 9*f(x), f(x))


Out[98]:
$$f{\left (x \right )} = C_{1} \sin{\left (3 x \right )} + C_{2} \cos{\left (3 x \right )}$$

In [99]:
dsolve(diff(f(x), x, 2) + 9*f(x), f(x), hint='default', ics={f(0):0, f(1):10})


Out[99]:
$$f{\left (x \right )} = C_{1} \sin{\left (3 x \right )} + C_{2} \cos{\left (3 x \right )}$$

In [141]:
# Essai de récupération de la valeur de la constante C1 quand une condition initiale est fournie
eqg = Symbol("eqg")
g = Function('g')
eqg = dsolve(Derivative(g(x), x) + g(x), g(x), ics={g(2): 50})
eqg


Out[141]:
$$g{\left (x \right )} = C_{1} e^{- x}$$

In [144]:
print "g(x) est de la forme {}".format(eqg.rhs)


g(x) est de la forme C1*exp(-x)

In [129]:
# recherche manuelle de la valeur de c1 qui vérifie la condition initiale
c1 = Symbol("c1")
c1 = solve(Eq(c1*E**(-2),50), c1)
print c1


[50*exp(2)]

SymPy ne sait pas résoudre cette equation différentielle non linéaire avec $h(x)^2$ :


In [112]:
h = Function('h')
try:
    dsolve(Derivative(h(x), x) + 0.001*h(x)**2 - 10, h(x))
except:
    print "une erreur s'est produite"


une erreur s'est produite

On peut résoudre cette équation différentielle avec une méthode numérique fournie par la fonction odeint de SciPy :

Méthode numérique pour équations différentielles (non SymPy)


In [110]:
from scipy.integrate import odeint

def dv_dt(vec, t, k, m, g):
    z, v = vec[0], vec[1]
    dz = -v
    dv = -k/m*v**2 + g
    return [dz, dv]

vec0 = [0, 0] # conditions initiales [altitude, vitesse]
t_si = numpy.linspace (0, 30 ,150) # de 0 à 30 s, 150 points
k = 0.1 # coefficient aérodynamique
m = 80 # masse (kg)
g = 9.81 # accélération pesanteur (m/s/s)
v_si = odeint(dv_dt, vec0, t_si, args=(k, m, g))

print "vitesse finale : {0:.1f} m/s soit {1:.0f} km/h".format(v_si[-1, 1], v_si[-1, 1] * 3.6)

fig_si, ax_si = plt.subplots()
ax_si.set_title("Vitesse en chute libre")
ax_si.set_xlabel("s")
ax_si.set_ylabel("m/s")
ax_si.plot(t_si, v_si[:,1], 'b')


vitesse finale : 88.4 m/s soit 318 km/h
Out[110]:
[<matplotlib.lines.Line2D at 0x1427d9e8>]

Pour aller plus loin